package com.weblog.search.service;

import com.weblog.search.client.ArticleClient;
import com.weblog.search.client.CategoryClient;
import com.weblog.search.client.UserClient;
import com.weblog.search.dao.ArticleDao;
import com.weblog.search.pojo.Article;
import com.weblog.search.pojo.ArticleExtension;
import entity.PageResult;
import entity.Result;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import util.IdWorker;
import util.MapSizeUtil;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * @author MaoLin Wang
 * @date 2019/9/2816:58
 */
@Service
public class ArticleService {

    @Autowired
    private ArticleDao articleDao;

    @Autowired
    private IdWorker idWorker;

    @Autowired
    private UserClient userClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ArticleClient articleClient;

    @Autowired
    private MapSizeUtil mapSizeUtil;


    /**
     * 发布文章、修改文章
     * @param aid 文章id
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Transactional(rollbackFor = Exception.class)
    public void save(String aid) throws InvocationTargetException, IllegalAccessException {
        Result result = articleClient.packageArticle(aid);
        System.out.println("触发了save");
        Map<String,Object>map = (Map<String, Object>) result.getData();
        Article article = new Article();
        BeanUtils.populate(article,map);
        articleDao.save(article);
    }


    /**
     * 封装前台分页结果集
     */
    private PageResult handlePageResult(int totalPage, List<Article>articles){
        List<ArticleExtension>articleExtensions=new ArrayList<>();
        for (Article article : articles) {
            ArticleExtension articleExtension=new ArticleExtension();
            //截取文章前130个字
            article.setContenthtml(article.getContenthtml().length()>=130?StringUtils.deleteWhitespace(article.getContenthtml().substring(0, 130)+"......"):StringUtils
                    .deleteWhitespace(article.getContenthtml()));
            //获取该文章的用户名和头像地址
            String userInfo= (String) userClient.findUserNameAndAvatarById(article.getUserid()).getData();
            String username = StringUtils.substringBefore(userInfo, ",");
            String avatar = StringUtils.substringAfter(userInfo, ",");

            articleExtension.setArticle(article);
            articleExtension.setUsername(username);
            articleExtension.setAvatar(avatar);

            articleExtensions.add(articleExtension);
        }
        return new PageResult(totalPage,articleExtensions);
    }
    /**
     * 分页查询（前台首页）
     *
     * @param page
     * @param size
     */
    public PageResult searchPage(int page, int size) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        //过滤字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"aid","categoryname","title","userid","contenthtml","commentnum","thumbup","ishot"}, null));

        //多字段查询
        nativeSearchQueryBuilder.withQuery(QueryBuilders.multiMatchQuery("1",new String[]{"state","ispublic"}));

        //分页
        Pageable pageable=PageRequest.of(page - 1, size);
        SearchQuery searchQuery = nativeSearchQueryBuilder.withPageable(pageable)
                .withSort(SortBuilders.fieldSort("publishtime").order(SortOrder.DESC))
                .build();
        //获取分页结果
        Page<Article> articlePage = articleDao.search(searchQuery);
        List<Article> articleList = articlePage.getContent();

        return handlePageResult(articlePage.getTotalPages(),articleList);

    }
    /**
     * 分页高亮关键字查询(前台)
     * @param
     * @param page 当前页
     * @param size 每页大小
     * @return
     */
    public PageResult searchHighlightPage(int page, int size, String key) {

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
       //过滤字段
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"aid","categoryname","title","userid","contenthtml","commentnum","thumbup","ishot"}, null));

        BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery();
        if(!"".equals(key)){
            //前缀标签
            String preTags="<font color=\"red\">";
            //后缀标签
            String postTags="</font>";
            //多字段查询
            boolQueryBuilder.must().add(QueryBuilders.multiMatchQuery(key,new String[]{"title","contenthtml"}));
            //不公开的过滤掉
            boolQueryBuilder.mustNot().add(QueryBuilders.termQuery("ispublic",0));
            //未审核的过滤掉
            boolQueryBuilder.filter().add(QueryBuilders.termQuery("state",1));

            HighlightBuilder highlightBuilder=new HighlightBuilder();

            //构建多条件高亮查询
            nativeSearchQueryBuilder.withQuery(boolQueryBuilder)
                    .withHighlightFields(new HighlightBuilder.Field("title").preTags(preTags).postTags(postTags),
                            new HighlightBuilder.Field("contenthtml").preTags(preTags).postTags(postTags));
            //分页
            Pageable pageable=PageRequest.of(page - 1, size);
            SearchQuery searchQuery = nativeSearchQueryBuilder.withPageable(pageable)
                    .withSort(SortBuilders.fieldSort("publishtime").order(SortOrder.DESC))
                    .build();
            //获取未高亮之前的结果集（有所有过滤后的字段）
            Page<Article> articles = articleDao.search(searchQuery);
            List<Article> rawArticle = articles.getContent();

            //执行分页查询
            AggregatedPage<Article>articlePage=elasticsearchTemplate.queryForPage(searchQuery, Article.class,
                    new SearchResultMapper() {
                        @Override
                        public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {

                            //定义查询出来内容存储的集合
                            List<Article> content = new ArrayList<>();
                            //获取高亮的结果
                            SearchHits searchHits = response.getHits();
                            if(searchHits!=null) {
                                //获取高亮中所有的内容
                                SearchHit[] hits = searchHits.getHits();
                                if(hits.length > 0) {
                                    for (SearchHit hit : hits) {
                                        Article article = new Article();
//                                     //高亮结果的id值(实体id)
                                        String id = hit.getId();
                                        //存入实体类
                                        article.setAid(id);

                                        //获取第一个字段的高亮内容
                                        HighlightField titleHighlight = hit.getHighlightFields().get("title");
                                        if(titleHighlight != null) {
                                            //获取第一个字段的值并封装给实体类
                                            String title = titleHighlight.getFragments()[0].toString();
                                            article.setTitle(title);
                                        }else {
                                            //获取原始的值
                                            String sourceTitle = (String) hit.getSourceAsMap().get("title");
                                            article.setTitle(sourceTitle);
                                        }
                                        HighlightField contentHighlight = hit.getHighlightFields().get("contenthtml");
                                        if(contentHighlight != null) {
                                            //获取第二个字段的值并封装给实体类
                                            String contentHtml = contentHighlight.getFragments()[0].toString();
                                            article.setContenthtml(contentHtml);
                                        }else {
                                            //获取原始的值
                                            String sourceContent = (String) hit.getSourceAsMap().get("contenthtml");
                                            article.setContenthtml(sourceContent);
                                        }
                                        content.add(article);
                                    }
                                }
                            }
                            return new AggregatedPageImpl<>((List<T>)content);

                        }
                    });
            //获取高亮查询后的结果集（只有标题和内容字段）
            List<Article> highLightArticles = articlePage.getContent();
            //遍历，将高亮后的结果替换到原来的结果中
            for(Article article:rawArticle){
                for(Article harticle:highLightArticles){
                    if(article.getAid().equals(harticle.getAid())){
                        article.setContenthtml(harticle.getContenthtml());
                        article.setTitle(harticle.getTitle());
                        break;
                    }
                }
            }
            return handlePageResult(articles.getTotalPages(),rawArticle);

        }else {
            return null;
        }


    }

    /**
     * 前台根据分类分类查询
     * @param page
     * @param size
     * @param cid
     * @return
     */
    public PageResult searchByCategory(int page, int size, String cid) {
        Result result = categoryClient.getLevelById(cid);
        int clevel = (int) result.getData();
        BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery();
        if(clevel==1){
            boolQueryBuilder.must(QueryBuilders.termQuery("cid1",clevel));
        }else {
            boolQueryBuilder.must().add(QueryBuilders.fuzzyQuery("cids",clevel==2?cid:StringUtils.substringAfterLast(cid,"/")));
            boolQueryBuilder.filter().add(QueryBuilders.termQuery("clevel",clevel));
        }
        Page<Article> articles = articleDao.search(boolQueryBuilder, PageRequest.of(page - 1, size));
       return handlePageResult(articles.getTotalPages(),articles.getContent());


    }
    /**
     * 分页多条件搜索
     *
     * @param searchMap
     * @param page
     * @param size
     * @return
     */
    public Page<Article> findSearch(Map searchMap, int page, int size) {
        Pageable pageable = PageRequest.of(page - 1, size);
        //创建bool查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //存储查询条件
        List<QueryBuilder> queryBuilders = new ArrayList<>();

        if (!"".equals((String) searchMap.get("categoryname"))) {
            queryBuilders.add(QueryBuilders.matchQuery("categoryname", searchMap.get("categoryname")));
        }
        if (!"".equals(searchMap.get("state"))) {
            /**
             * 2：全部，不添加查询条件
             * 1：已审核
             * 0：未审核
             */
            if (!"2".equals((String) searchMap.get("state"))) {
                queryBuilders.add(QueryBuilders.matchQuery("state", (String) searchMap.get("state")));
            }
        }
        if (!"".equals(searchMap.get("ishot"))) {
            /**
             * 1：热门
             * 0：全部，不添加查询条件
             */
            if ("1".equals((String) searchMap.get("ishot"))) {
                queryBuilders.add(QueryBuilders.matchQuery("ishot", (String) searchMap.get("ishot")));
            }
        }
        if (searchMap.get("key") != null && !"".equals(searchMap.get("key"))) {
            //多字段查询匹配关键词
            queryBuilders.add(QueryBuilders.multiMatchQuery((String) searchMap.get("key"), "title", "content_md"));
        }
        //以上条件构建到bool查询中
        boolQueryBuilder.must().addAll(queryBuilders);
        //日期范围
        List<String> daterangList = (List<String>) searchMap.get("daterange");

        if (daterangList.size() > 0 && daterangList != null) {
            //添加过滤，应使用gte和lte，大于等于/小于等于，gt和lt为大于/小于
            boolQueryBuilder.filter().add(QueryBuilders.rangeQuery("publishtime").gte(daterangList.get(0)).lte(daterangList.get(1)));//添加过滤
        }
        return articleDao.search(boolQueryBuilder, pageable);

    }

    /**
     * 根据分类id集合和分类等级获取各分类下的文章数和文章id集合
     *
     * @param level   分类等级
     * @param cidList 该等级下的所有分类id
     * @return
     */
    public Map<String, Object> fetchTotalByCategory(int level, List<String> cidList) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        final String CID1_AGG = "cid1_agg";
        final String CATEGORY_AGG = "category_aggs";

        if (level == 1) {
            //一级分类
            //添加聚合
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(CID1_AGG).field("cid1"));
            //执行查询，获得结果集
            AggregatedPage<Article> aggregatedPage = (AggregatedPage<Article>) articleDao.search(nativeSearchQueryBuilder.build());
            //得到集合对象
            return handlefetchTotalByCategory(level, cidList, aggregatedPage.getAggregation(CID1_AGG));
        } else {
            //二级分类或以下
            //按cids作聚合
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(CATEGORY_AGG).field("cids"));
            //只查对应等级的分类
            nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("clevel", level));
            //查询聚合结果集
            AggregatedPage<Article> aggregatedPage = (AggregatedPage<Article>) articleDao.search(nativeSearchQueryBuilder.build());
            return handlefetchTotalByCategory(level, cidList, aggregatedPage.getAggregation(CATEGORY_AGG));
        }
    }

    /**
     * 根据聚合对象封装结果
     *
     * @param level       分类等级
     * @param cidList     cid集合
     * @param aggregation 聚合对象
     * @return
     */
    private Map<String, Object> handlefetchTotalByCategory(int level, List<String> cidList, Aggregation aggregation) {
        StringTerms stringTerms = (StringTerms) aggregation;
        //取出桶
        List<StringTerms.Bucket> buckets = stringTerms.getBuckets();
        //存储已有文章的分类（key）和数量（value）
        Map<String, Long> map = new HashMap<>(mapSizeUtil.initCapacity(buckets.size()));
        //遍历桶，将分类id和对应的数量放入map中
        if (level == 1) {
            buckets.forEach(bucket -> {
                map.put(bucket.getKeyAsString(), bucket.getDocCount());
            });
        } else {
            buckets.forEach(bucket -> {
                //等级为2时，key只有一个值，等级大于2时，key的格式为312312/2312/23544,截取最后一部分
                map.put(level == 2 ? bucket.getKeyAsString() : StringUtils.substringAfterLast(bucket.getKeyAsString(), "/"), bucket.getDocCount());
            });
        }
        //拿到分类id的集合
        Set<String> keySet = map.keySet();
        /**
         * 获得各分类下的文章id的map集合
         * map的key:文章中使用的分类id
         * map的value：对应aid集合
         */
        List<Map<String, Object>> aidsList = new ArrayList<>();
        Map<String, Object> aidMap = new HashMap<>(mapSizeUtil.initCapacity(buckets.size()));

        for (String key : keySet) {
            //查询对应文章id集合
            aidMap.put(key, queryAidsByCids(key, level));
            aidsList.add(aidMap);
        }

        //存储最终返回的各分类文章数量集合
        List<Long> countList = new ArrayList<>();
        //存储最终返回文章id集合的map，key为所有分类id
        Map<String, Object> aidsResultMap = new HashMap<>();
        //最终返回的集合
        Map<String, Object> resultMap = new HashMap<>(mapSizeUtil.initCapacity(cidList.size()));

        //遍历所需分类id集合
        for (String cid : cidList) {
            //已用id存在该id
            if (keySet.contains(cid)) {
                //存入文章数量
                //将该id和对应的值存入结果集中
                countList.add(map.get(cid));
                aidsResultMap.put(cid, aidMap.get(cid));
            } else { //没有匹配的id
                //对应分类的文章数量置0
                countList.add(0L);
                //对应的文章id集合置空
                aidsResultMap.put(cid, null);
            }
        }

        /*
            封装结果集
         */
        resultMap.put("articleCountList", countList);
        resultMap.put("aidsMap", aidsResultMap);

        return resultMap;

    }

    /**
     * 根据cid查询文章id集合
     *
     * @param cids 分类集合
     * @return 待查询文章id集合
     */
    private List<String> queryAidsByCids(String cids, int level) {
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        if (level == 1) { //分类等级为1
            //通过cid1精准查询
            nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("cid1", cids));
        } else if (level == 2) { //分类等级为2，cids字段只有一个值
            //使用精准查询
            nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("cids", cids));
        } else {//等级大于2，cids有多个值

            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            //模糊查询cids
            QueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("cids.text", cids);
            boolQueryBuilder.must().add(fuzzyQueryBuilder);
            //添加过滤，指定分类level，cids有多个值时，某个值只会在当前分类等级或低于当前分类等级的文章的cids出现，所以指定clevel的后，就可以确定查询内容
            boolQueryBuilder.filter().add(QueryBuilders.termQuery("clevel", level));

            nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        }
        //结果集过滤，只需要文章aid
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"aid"}, null));
        //执行搜索
        Page<Article> search = articleDao.search(nativeSearchQueryBuilder.build());
        //得到文章集合
        List<Article> articles = search.getContent();
        //封装文章id集合
        List<String> aids = new ArrayList<>();
        for (Article article : articles) {
            aids.add(article.getAid());
        }
        return aids;
    }


    /**
     * 求文章总数
     *
     * @return
     */
    public long sum() {
        return articleDao.count();
    }

    /**
     * 查询热门文章前20个
     *
     * @return
     */
    public Map<String, Object> fetchHotArticle() {
        List<Article> articles = articleDao.findTop20ByIshotAndStateOrderByThumbupDesc("1", "1");

        //最终返回结果集
        Map<String, Object> map = new HashMap<>(mapSizeUtil.initCapacity(1));

        //存储前端data中的数据 data类型：[[],[],[],[]]
        List<Object> dataList = new ArrayList<>();

        List<String> datainfoList;
        /**
         * 封装data数据
         */
        for (Article article : articles) {
             datainfoList = new ArrayList<>();
            datainfoList.add(0, article.getAid());
            datainfoList.add(1, article.getTitle());

            Result result = userClient.findUserNameById(article.getUserid());
            String username = (String) result.getData();
            datainfoList.add(2, username);

            datainfoList.add(3, article.getThumbup().toString());

            dataList.add(datainfoList);
        }
        map.put("data", dataList);
        return map;
    }


    /**
     * 删除
     * @param aid
     */
    public void delete(String aid) {
        articleDao.deleteById(aid);
    }

    /**
     * 后台软删除
     * @param aid
     */
    public void black(String aid) {
        Article article = articleDao.findById(aid).get();
        article.setState("0");
        articleDao.save(article);
    }

    /**
     * 点赞
     * @param aid
     */
    public void thumbup(String aid) {
        Article article = articleDao.findById(aid).get();

        article.setThumbup(article.getThumbup()+1);
        articleDao.save(article);
    }

    /**
     * 增加评论数
     * @param aid
     */
    public void comment(String aid) {
        Article article = articleDao.findById(aid).get();
        article.setCommentnum(article.getCommentnum()+1);
        articleDao.save(article);
    }
}

